//----------------------------------------------------------
//	asset file manager
//----------------------------------------------------------
#include "../Common.h"
//#include "compression/xfile.h"
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//	CAssetFile class contains an CAssetFile
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
typedef struct
{
	void	*ptr;
	u32		type;
	u32		length;

} SAssetFileBlock;

class CAssetFile : public IAssetFile
{
public:
	CAssetFile() :	next(0),ppme(0),
					index(0),entries(0),
					blockIndices(0),dictionary(0),
					pBlocks(0)
	{
	}

	//----------------------------------------------------------
	//	IAssetInit implementation
	//----------------------------------------------------------
	const char	*GetName( HASH hash ) const;
	void		*GetChunk( HASH assetName, LOCID id, u32 *length ) const;
	void		SetupHashList( CStaticHashList *pDict, void *base, bool doItems=true ) const;

public:
	CAssetFile			*next;
	CAssetFile			**ppme;

	void				*context;

	char				filename[_MAX_PATH];
	CMasterIndex		*index;
	CMasterIndexEntry	*entries;
	CBlockIndex			*blockIndices;
	CStaticHashList		*dictionary;
	SAssetFileBlock		*pBlocks;
};

//----------------------------------------------------------
//	IAssetInit implementation
//----------------------------------------------------------
const char *CAssetFile::GetName( HASH hash ) const
{
	if( dictionary )
		return (const char *)dictionary->FindByHash(hash);
	return 0;
}

void *CAssetFile::GetChunk( HASH assetName, LOCID id, u32 *length ) const
{
	CBlockIndex			*pbi;
	CBlockIndexEntry	*pbe;
	u32					i,j;

	if( blockIndices!=0 && pBlocks!=0 && index!=0 )
	{
		pbi = blockIndices;
		for( i=0;i<index->numBlocks;i++ )
		{
			if( pbi->blockType==id.blockId && pBlocks[i].ptr!=0 )
			{
				pbe = (CBlockIndexEntry*)IncPointer(pbi,sizeof(CBlockIndex));

				// this is non-time-critical so just do linear search...
				for( j=0;j<pbi->numItems;j++,pbe++ )
				{
					if( pbe->nameHash==assetName &&
						pbe->locatorId.id==id.id )
					{
						if( length )
							*length = pbe->dataLength;
						return IncPointer(pBlocks[i].ptr,pbe->blockOffset);
					}
				}
			}
			pbi = IncPointer(pbi,pbi->indexLength);
		}
	}
	return 0;
}

void CAssetFile::SetupHashList( CStaticHashList *pDict, void *base, bool doItems ) const
{
	CHashListItem	*pItems = (CHashListItem*)pDict->GetData();
	u32				numItems = pDict->GetNItems();
	u32				i;

	pItems = OffsetToPointer(pItems,base);

	if( doItems )
	{
		for( i=0;i<numItems;i++ )
		{
			pItems[i].data = OffsetToPointer(pItems[i].data,base);
		}
	}

	pDict->SetData(numItems,pItems);
}

//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//	CAssetFileMgr constructor/destructor
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
CAssetFileMgr::CAssetFileMgr( ASSETALLOCFN a, ASSETFREEFN f )
{
	pAssets = pFilesToLoad = pFilesToInit = pFilesToUnload = 0;
	allocfn = a;
	freefn = f;
	numAssetTypes = 0;
}

CAssetFileMgr::~CAssetFileMgr()
{
	UnloadAll();
}

//----------------------------------------------------------
//	add an asset type
//----------------------------------------------------------
void CAssetFileMgr::AddAssetType( const char *name, ASSETINITFN ifn, ASSETUNINITFN ufn, ASSETFIXUPFN ffn )
{
	HASH			hash;
	SAssetProcType	*pProc;

	hash = MakeStringHash(name);
	pProc = FindAssetType(hash);
	if( pProc==0 )
	{
		if( numAssetTypes==countof(assetTypes) )
		{
			assert("Too many asset types");
			return;
		}
		pProc = &assetTypes[numAssetTypes++];
	}

	pProc->typeHash = hash;
	pProc->initFn   = ifn;
	pProc->uninitFn = ufn;
	pProc->fixupFn  = ffn;
}

//----------------------------------------------------------
//	find an asset type
//----------------------------------------------------------
SAssetProcType *CAssetFileMgr::FindAssetType( HASH typeHash )
{
	u32		i;
	for( i=0;i<numAssetTypes;i++ )
	{
		if( assetTypes[i].typeHash==typeHash )
			return &assetTypes[i];
	}
	return 0;
}

//----------------------------------------------------------
//	find an asset file
//----------------------------------------------------------
CAssetFile *CAssetFileMgr::FindFile( const char *filename, CAssetFile **pList )
{
	CAssetFile	*ret = 0;

	for( ret=pList[0];ret;ret=CUniList::GetNext(ret) )
	{
		if( STRING::Compare(filename,ret->filename,false)==0 )
			break;
	}

	return ret;
}

CAssetFile *CAssetFileMgr::FindFile( const char *filename, bool onlyLoaded )
{
	if( !onlyLoaded )
	{
		CAssetFile	*ret;

		ret = FindFile(filename,&pFilesToLoad);
		if( ret )	return ret;

		ret = FindFile(filename,&pFilesToInit);
		if( ret )	return ret;
	}

	return FindFile(filename,&pAssets);
}

//----------------------------------------------------------
//	process
//----------------------------------------------------------
void CAssetFileMgr::Process()
{
	while( LoadNextFile() )
	{
		InitializeNextFile();
	}

	while( UnloadNextFile() )
	{
	}
}

//----------------------------------------------------------
//	load an asset file
//----------------------------------------------------------
bool CAssetFileMgr::Load( const char *filename, void* context, bool wait )
{
	CAssetFile *pFile;

	if( FindFile(filename,false) )
		return true;

	pFile = new CAssetFile();
	if( pFile==0 )
		return false;

	this->context = context;

	STRING::Copy(pFile->filename,filename,countof(pFile->filename));
	CUniList::Insert(pFile,&pFilesToLoad);

	if( wait )
	{
		Process();
		return (FindFile(filename)!=0);
	}
	return true;
}

//----------------------------------------------------------
//	unload an asset file
//----------------------------------------------------------
void CAssetFileMgr::Unload( const char *filename, bool wait )
{
	CAssetFile	*ptr;

	ptr = FindFile(filename,false);
	if( ptr )
	{
		CUniList::Reparent(ptr,&pFilesToUnload);

		if( wait )
			Process();
	}
}

//----------------------------------------------------------
//	unload all asset files
//----------------------------------------------------------
void CAssetFileMgr::UnloadAll( bool wait )
{
	while( pAssets )
		CUniList::Reparent(pAssets,&pFilesToUnload);

	while( pFilesToLoad )
		CUniList::Reparent(pFilesToLoad,&pFilesToUnload);

	while( pFilesToInit )
		CUniList::Reparent(pFilesToInit,&pFilesToUnload);

	if( wait )
		Process();
}

//----------------------------------------------------------
//	find an asset
//----------------------------------------------------------
void *CAssetFileMgr::FindAsset( HASH assetName, HASH assetType, CAssetFile *pFile )
{
	void				*ret = 0;
	CMasterIndexEntry	*pem = pFile->entries;
	CMasterIndexEntry	*testme;
	s32					window[3];

	window[0] = 0;
	window[2] = pFile->index->numItems;

	while( window[0]<=window[2] )
	{
		window[1] = (window[0] + window[2])/2;
		testme = &pem[window[1]];

		if( testme->nameHash<assetName )
		{
			window[0] = window[1] + 1;
		}
		else if( testme->nameHash>assetName )
		{
			window[2] = window[1] - 1;
		}
		else
		{
			if( testme->typeHash==assetType )
				ret = testme->ptr;
			break;
		}
	}

	return ret;
}
void *CAssetFileMgr::FindAsset( const char *assetName, const char *assetType, const char *inFilename )
{
	return FindAsset(MakeStringHash(assetName),MakeStringHash(assetType),inFilename);
}

void *CAssetFileMgr::FindAsset( HASH assetHash, const char *assetType, const char *inFilename )
{
	return FindAsset(assetHash,MakeStringHash(assetType),inFilename);
}
	
void *CAssetFileMgr::FindAsset( HASH name,HASH type, const char *inFilename )
{
	CAssetFile	*pFile;
	void		*ret = 0;

	for( pFile=pAssets;pFile;pFile=CUniList::GetNext(pFile) )
	{
		if( inFilename==0 || STRING::Compare(inFilename,pFile->filename,false)==0 )
		{
			ret = FindAsset(name,type,pFile);
			if( ret!=0 )
				break;
		}
	}

	return ret;
}


//----------------------------------------------------------
//	enumerate assets
//----------------------------------------------------------
u32		CAssetFileMgr::EnumerateAssets( ASSETENUMFN fn, void *context, HASH type, const CAssetFile *pFile )
{
	CMasterIndexEntry	*pem = pFile->entries;
	u32					i,num;

	for( i=pFile->index->numItems,num=0;i>0;--i,pem++ )
	{
		if( type==0 || type==pem->typeHash )
		{
			if( fn==0 || fn(this,context,pem->ptr,pFile->GetName(pem->nameHash),pem->typeHash) )
				num++;
		}

	}
	return num;
}


u32		CAssetFileMgr::EnumerateAssets( ASSETENUMFN fn, void *context, const char *type, const char *inFilename )
{
	CAssetFile	*pFile;
	u32			num = 0;
	HASH		typeHash = MakeStringHash(type);

	for( pFile=pAssets;pFile;pFile=CUniList::GetNext(pFile) )
	{
		if( inFilename==0 || STRING::Compare(inFilename,pFile->filename,false)==0 )
		{
			num += EnumerateAssets(fn,context,typeHash,pFile);

			if( inFilename!=0 )
				return num;
		}
	}

	return num;
}

//----------------------------------------------------------
//	fixup assets
//----------------------------------------------------------
u32		CAssetFileMgr::FixupAssets( HASH type, const CAssetFile *pFile )
{
	CMasterIndexEntry	*pem = pFile->entries;
	u32					i,num;
	SAssetProcType		*pType;

	for( i=pFile->index->numItems,num=0;i>0;--i,pem++ )
	{
		if( type==0 || type==pem->typeHash )
		{
			pType = FindAssetType(pem->typeHash);

			if( pType && pType->fixupFn )
			{
				pType->fixupFn(this,pFile,pem->ptr,pem);
				num++;
			}
		}
	}
	return num;
}


u32		CAssetFileMgr::FixupAssets( const char *type, const char *inFilename )
{
	CAssetFile	*pFile;
	u32			num = 0;
	HASH		typeHash = MakeStringHash(type);

	for( pFile=pAssets;pFile;pFile=CUniList::GetNext(pFile) )
	{
		if( inFilename==0 || STRING::Compare(inFilename,pFile->filename,false)==0 )
		{
			num += FixupAssets(typeHash,pFile);

			if( inFilename!=0 )
				return num;
		}
	}

	return num;
}

//----------------------------------------------------------
//	enumerate memory blocks
//----------------------------------------------------------
void	CAssetFileMgr::EnumerateMemoryBlocks( BLOCKENUMFN fn, void *context )
{
	CAssetFile	*pFile;
	u32			i;

	for( pFile=pAssets;pFile;pFile=CUniList::GetNext(pFile) )
	{
		if( pFile->pBlocks )
		{
			for( i=0;i<pFile->index->numBlocks;i++ )
			{
				if( pFile->pBlocks[i].ptr!=0 && fn )
				{
					fn(context,pFile->pBlocks[i].ptr,pFile->pBlocks[i].length);
				}
			}
		}
	}
}

//----------------------------------------------------------
//	informational output
//----------------------------------------------------------
void CAssetFileMgr::Info( const char *fmt, ... )
{
	va_list		args;
	char		buffer[1024];

	if( fmt )
	{
		va_start(args, fmt);
		vsprintf(buffer, fmt, args);
		va_end(args);

		OutputDebugStringA(buffer);
	}
	OutputDebugStringA("\r\n");
}

//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//	load the next file
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
bool CAssetFileMgr::LoadNextFile( )
{
	CAssetFile	*pLoadMe;
	CBlockIndex	*pbi;
	HANDLE		hFile;
	/*XFILEREAD	file;*/
	u32			i,size;
	bool		ret = false;
	DWORD		dwRead;

	// is there a file to load
	pLoadMe = pFilesToLoad;
	if( pLoadMe==0 )
		return ret;
	CUniList::Remove(pLoadMe);

	Info("Loading file %s",pLoadMe->filename);

	if( (hFile = CreateFileA( pLoadMe->filename, GENERIC_READ, 0, NULL, 
							  OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ))!=INVALID_HANDLE_VALUE )
	/*if( file.Open( pLoadMe->filename ) )*/
	{
		do
		{
			CMasterIndex	index;

			//---------------------------------
			//	load the master index
			//---------------------------------
			// read the index header
			if( !ReadFile( hFile, &index, sizeof(index), &dwRead, NULL ) )
				break;
			/*if( !file.Read( &index, sizeof(index) ) )
				break;*/

			Info("Master index is %d bytes long",index.indexLength);

			// allocate master index, dictionary and pointers for allocated block memory
			size = index.indexLength + index.dictionaryLength + index.numBlocks*sizeof(SAssetFileBlock);
			pLoadMe->index = (CMasterIndex*)allocfn(BLOCKTYPE_NORMAL,size,5);
			if( pLoadMe->index==0 )
				break;
			pLoadMe->dictionary = (CStaticHashList*)IncPointer(pLoadMe->index,index.indexLength);
			pLoadMe->pBlocks    = (SAssetFileBlock*)IncPointer(pLoadMe->dictionary,index.dictionaryLength);

			// read the index in
			pLoadMe->index[0] = index;
			pLoadMe->entries = (CMasterIndexEntry*)IncPointer(pLoadMe->index,sizeof(CMasterIndex));
			if( !ReadFile( hFile, pLoadMe->entries, index.indexLength-sizeof(CMasterIndex), &dwRead, NULL ) )
				break;
			/*if( !file.Read( pLoadMe->entries, index.indexLength-sizeof(CMasterIndex) ) )
				break;*/
	
			//---------------------------------
			//	load the block indices
			//---------------------------------

			Info("Block indices are %d bytes long",index.totalBlockIndexLength);
			
			// allocate memory for the block indices
			pLoadMe->blockIndices = (CBlockIndex*)allocfn(BLOCKTYPE_NORMAL,index.totalBlockIndexLength,5);
			if( pLoadMe->blockIndices==0 )
				break;

			// read the block indices in
			if( !ReadFile( hFile, pLoadMe->blockIndices, index.totalBlockIndexLength, &dwRead, NULL ) )
				break;
			/*if( !file.Read( pLoadMe->blockIndices, index.totalBlockIndexLength ) )
				break;*/

			//---------------------------------
			//	load the blocks
			//---------------------------------
			// read each of the blocks in
			pbi = pLoadMe->blockIndices;
			for( i=0;i<index.numBlocks;i++ )
			{
				Info("Block %d is %d bytes long",i,pbi->blockLength);

				pLoadMe->pBlocks[i].type   = pbi->blockType;
				pLoadMe->pBlocks[i].length = pbi->blockLength;
				pLoadMe->pBlocks[i].ptr    = allocfn(pbi->blockType,pbi->blockLength,pbi->blockAlignment);
				if( pLoadMe->pBlocks[i].ptr==0 )
					break;
				if( !ReadFile( hFile, pLoadMe->pBlocks[i].ptr, pbi->blockLength, &dwRead, NULL ) )
					break;
				/*if( !file.Read( pLoadMe->pBlocks[i].ptr, pbi->blockLength ) )
					break;*/

				pbi = IncPointer(pbi,pbi->indexLength);
			}
			if( i!=index.numBlocks )
				break;

			//---------------------------------
			// finally, load the dictionary...
			//---------------------------------
			Info("Dictionary is %d bytes long",index.dictionaryLength);
			
			if( index.dictionaryLength )
			{
				// dictionary was allocated with CMasterIndex
				// read the dictionary in
				if( !ReadFile( hFile, pLoadMe->dictionary, index.dictionaryLength, &dwRead, NULL ) )
					break;
				/*if( !file.Read( pLoadMe->dictionary, index.dictionaryLength ) )
					break;*/

				// set up the dictionary
				pLoadMe->SetupHashList(pLoadMe->dictionary,pLoadMe->dictionary);

			}

			ret = true;

	
		} while(0);

		/*file.Close();*/
		CloseHandle( hFile );
	}
	
	CUniList::Insert(pLoadMe,ret?&pFilesToInit:&pFilesToUnload);
	return ret;
}

//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//	Initialize the next file
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
bool CAssetFileMgr::InitializeNextFile()
{
	CAssetFile			*pFile;
	CMasterIndex		*pmi;
	CMasterIndexEntry	*pme;
	SAssetProcType		*pProc;
	u32					i;
	bool				ret = false;

	pFile = pFilesToInit;
	if( pFile==0 )
		return ret;
	CUniList::Remove(pFile);

	ret = true;

	//---------------------------------
	//	initialize each of the items
	//	in the master index
	//---------------------------------
	pmi = pFile->index;
	pme = pFile->entries;
	for( i=0;i<pmi->numItems;i++,pme++ )
	{
		if( pme->ptr==0 )
		{
			Info("%s(%s);",pFile->GetName(pme->typeHash),pFile->GetName(pme->nameHash));
			pProc = FindAssetType(pme->typeHash);
			if( pProc && pProc->initFn )
			{
				pme->ptr = pProc->initFn( pFile, pme, context );
			}
		}
	}

	//---------------------------------
	//	free the block indices
	//---------------------------------
	freefn(pFile->blockIndices,BLOCKTYPE_NORMAL);
	pFile->blockIndices = 0;

	//---------------------------------
	//	free any temporary memory
	//	allocated for the blocks
	//---------------------------------
	for( i=0;i<pmi->numBlocks;i++ )
	{
		if( pFile->pBlocks[i].type==BLOCKTYPE_TEMP && 
			pFile->pBlocks[i].ptr!=0 )
		{
			freefn(pFile->pBlocks[i].ptr,pFile->pBlocks[i].type);
			pFile->pBlocks[i].ptr = 0;
		}
	}

	//---------------------------------
	//	move the assetfile into the 
	//	list of available files
	//---------------------------------
	CUniList::Insert(pFile,ret?&pAssets:&pFilesToUnload);
	return true;
}


//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//	Unload the next file
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
//----------------------------------------------------------
bool CAssetFileMgr::UnloadNextFile( )
{
	CAssetFile			*pFreeMe;
	SAssetProcType		*pProc;
	CMasterIndex		*pmi;
	CMasterIndexEntry	*pme;
	u32			i;

	// is there a file to load
	pFreeMe = pFilesToUnload;
	if( pFreeMe==0 )
		return false;
	CUniList::Remove(pFreeMe);

	// uninitialize the data members
	pmi = pFreeMe->index;
	pme = pFreeMe->entries;
	if( pmi && pme )
	{
		for( i=0;i<pmi->numItems;i++,pme++ )
		{
			if( pme->ptr!=0 )
			{
				pProc = FindAssetType(pme->typeHash);
				if( pProc && pProc->uninitFn )
				{
					pProc->uninitFn(pme->ptr,pFreeMe->context);
					pme->ptr = 0;
				}
			}
		}
	}

	// free the memory
	if( pFreeMe->blockIndices )
		freefn(pFreeMe->blockIndices,BLOCKTYPE_NORMAL);

	if( pFreeMe->index )
	{
		// free the block memory
		for( i=0;i<pFreeMe->index->numBlocks;i++ )
		{
			if( pFreeMe->pBlocks[i].ptr )
			{
				freefn(pFreeMe->pBlocks[i].ptr,pFreeMe->pBlocks[i].type);
			}
		}

		freefn(pFreeMe->index,BLOCKTYPE_NORMAL);
	}

	// finally free the CAssetFile
	delete pFreeMe;
	return true;
}
